using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading.Tasks;
using System.Windows.Forms;
using Chess.Properties;
using Chess.Source;
using ChessEngine.Engine;
using System.Linq;

using N = ChessEngine.Native;
using System.Collections.Generic;

namespace Chess.Components
{
    public partial class ChessBoard : UserControl
    {
        #region Internal

        #region Nested type: Selection

        internal class Selection
        {
            public byte Column;
            public byte Row;
            public bool Selected;

            public byte Packed
            {
                get
                {
                    return (byte)(Row * 8 + Column);
                }
                set
                {
                    Column = (byte)(value % 8);
                    Row = (byte)(value / 8);
                }
            }
        }

        #endregion

        

        #endregion

        #region Enumerators

        #region Column enum

        public enum Column
        {
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            Unknown
        }

        #endregion

        #region DisplayChessPieceType enum

        public enum DisplayChessPieceType
        {
            ClassicHiRes,
            Classic,
            EnchancedClassic
        }

        #endregion

        #endregion

        #region Delegates

        public delegate void TurnChangedHandler(N.PieceColor whoseMove);

        #endregion

        #region PrivateMembers

        private Selection currentDestination;
        private Selection currentSource;

        //private Engine engine;
        private N.IEngine engine;
        private int boxHeight;
        private int maxHeight;

        private bool _whiteIsAI = false;
        private bool _blackIsAI = false;
        #endregion

        #region PublicMembers

        public event TurnChangedHandler TurnChanged;
       
       
        #endregion

        #region Constructors

        public ChessBoard()
        {
            InitializeComponent();

            // The first game by default uses black as an AI player
            NewGame(false, true, true);           
        }

        public void NewGame(bool whiteIsAI, bool blackIsAI, bool useNativeEngine)
        {
            _whiteIsAI = whiteIsAI;
            _blackIsAI = blackIsAI;

            //ChessEngine 
            if (engine != null)
                engine.Dispose();

            engine = useNativeEngine ? (N.IEngine)new N.NativeEngine(blackIsAI ? "basic" : "", whiteIsAI ? "basic" : "") :
                                       (N.IEngine)new Engine();
      
            currentSource = new Selection();
            currentDestination = new Selection();

            OnTurnChanged();

            //if (engine.HumanPlayer != engine.WhoseMove)
            //{
            //    EngineMove();
            //}

            Refresh(); 

        }

        #endregion

        #region Public Properties
        public bool IsBlackAI
        {
            get
            {
                return _blackIsAI;
            }
        }
        public bool IsWhiteAI
        {
            get
            {
                return _whiteIsAI;
            }
        }
        #endregion

        #region PublicMethods


        public static Column GetColumnFromInt(int column)
        {
            Column retColumnt;

            switch (column)
            {
                case 1:
                    retColumnt = Column.A;
                    break;
                case 2:
                    retColumnt = Column.B;
                    break;
                case 3:
                    retColumnt = Column.C;
                    break;
                case 4:
                    retColumnt = Column.D;
                    break;
                case 5:
                    retColumnt = Column.E;
                    break;
                case 6:
                    retColumnt = Column.F;
                    break;
                case 7:
                    retColumnt = Column.G;
                    break;
                case 8:
                    retColumnt = Column.H;
                    break;
                default:
                    retColumnt = Column.Unknown;
                    break;
            }

            return retColumnt;
        }

        #endregion

        #region Private Methods

        private void EngineMove(N.Move? move = null)
        {
            if (move == null)
            {
                if (engine.GetPieceTypeAt(currentSource.Column, currentSource.Row) == N.PieceType.None)
                {
                    currentDestination.Selected = false;
                    currentSource.Selected = false;
                    return;
                }

                //Check if this is infact a valid move
                if (!IsValidMove(out move))
                {
                    currentSource.Selected = false;
                    currentDestination.Selected = false;
                    return;
                }
            }

            engine.DoMove(move.Value);

            Refresh();

            if (engine.IsGameOver)
            {
                if (engine.IsCheckmate)
                {
                    string player = engine.IsBlackMate ? "White" : "Black";

                    MessageBox.Show("Checkmate! " + player + " wins!");
                }
                else if (engine.IsStalemate)
                {
                    MessageBox.Show("Stalemate!");
                }

                NewGame(_whiteIsAI, _blackIsAI, engine is N.NativeEngine);
            }
            else
            {
                OnTurnChanged();

                //Clear Source for next Selection             
                currentSource.Selected = false;
            }
        }

        private bool IsValidMove(out N.Move? move)
        {
            move = null;

            N.Piece p = engine.GetPieceAt(currentSource.Column, currentSource.Row);

            if (p.Type == N.PieceType.None)
                return false;

            if (p.Color != engine.WhoseMove)
                return false;

            return (move = GetCurrentMove()) != null;
        }
        private N.Move? GetCurrentMove()
        {
            N.Move[] moves = (from m in engine.GetMoves()
                              where m.SrcPos1 == (currentSource.Row * 8 + currentSource.Column) &&
                                    m.DestPos1 == (currentDestination.Row * 8 + currentDestination.Column)
                              select m).ToArray();

            return moves.Length != 0 ? moves[0] : (N.Move?)null;
        }

        private void OnTurnChanged()
        {
            if (TurnChanged != null)
            {
                TurnChanged(engine.WhoseMove);
            }

            if (engine.WhoseMove == N.PieceColor.White && _whiteIsAI)
            {
                DoAIMoveAsync();
            }
            else if (engine.WhoseMove == N.PieceColor.Black && _blackIsAI)
            {
                DoAIMoveAsync();
            }
        }

        private async void DoAIMoveAsync()
        {
            // Compute the next move in a background thread
            N.Move move = await Task.Factory.StartNew(() => ComputeAIMove());

            // Now that computation is finished, we need to call the EngineMove on the UI thread to update the board state.
            // The body of the BeginInvoke function will be executed on the UI thread where it is safe to do UI stuff
            BeginInvoke(new Action(() =>
                {
                    EngineMove(move);
                }));
        }

        private N.Move ComputeAIMove()
        {
            N.Move m = engine.DoAIMove();

            currentSource.Packed = m.SrcPos1;
            currentDestination.Packed = m.DestPos1;

            return m;
        }

        #endregion

        #region Events

        private void ChessBoard_Paint(object sender, PaintEventArgs e)
        {
            GraphicsBuffer graphicsBuffer = new GraphicsBuffer();
            graphicsBuffer.CreateGraphicsBuffer(null, Width, Height);

            Graphics g = graphicsBuffer.Graphics;
           
            SolidBrush solidWhiteBrush = new SolidBrush(Color.White);
            SolidBrush solidBlackBrush = new SolidBrush(Color.Black);
         
            Pen penBlack = new Pen(Color.Black, 1);
 
            Pen penHightlight = new Pen(Color.Black, 2);
            Pen penDestination = new Pen(Color.Yellow, 2);
            Pen penValidMove = new Pen(Color.Black, 2);
            Pen penEnPassant = new Pen(Color.DeepPink, 1);

            const int buffer = 10;

            if (Width < Height)
            {
                maxHeight = Width - 5 - buffer;
                boxHeight = maxHeight/8;
            }
            else
            {
                maxHeight = Height - 5 - buffer;
                boxHeight = maxHeight/8;
            }

            g.Clear(BackColor);

            try
            {
                int selectedX;
                int selectedY;

                //Draw Chess Board
                for (byte y = 0; y < 8; y++)
                {
                    for (byte x = 0; x < 8; x++)
                    {
                        if ((x + y)%2 == 0)
                        {
                            g.FillRectangle(solidWhiteBrush, (x * boxHeight) + buffer, (y * boxHeight) , boxHeight, boxHeight);
                        }
                        else
                        {
                            Rectangle drawArea1 = new Rectangle((x * boxHeight) + buffer, (y * boxHeight), boxHeight, boxHeight);
                            LinearGradientBrush linearBrush = new LinearGradientBrush(
                                        drawArea1, Color.Gainsboro, Color.Silver, LinearGradientMode.ForwardDiagonal );
                            g.FillRectangle(linearBrush, (x * boxHeight) + buffer, (y * boxHeight), boxHeight, boxHeight);
                        }

                        g.DrawRectangle(penBlack, (x * boxHeight) + buffer, (y * boxHeight) , boxHeight, boxHeight);
                    }
                    
                }
                for (byte i = 0; i < 8; i++)
                {
                    g.DrawString((8 - i).ToString(), new Font("Verdana", 8), solidBlackBrush, 0, (i * boxHeight)+ buffer);
                    g.DrawString(GetColumnFromInt(i + 1).ToString(), new Font("Verdana", 8), solidBlackBrush, (i * boxHeight) + (boxHeight/2) + 3, maxHeight - 1);
                }
                //Draw Pieces

                for (byte column = 0; column < 8; column++)
                {
                    for (byte row = 0; row < 8; row++)
                    {
                        N.PieceType chessPieceType = engine.GetPieceTypeAt(column, row);
                        
                        if (chessPieceType != N.PieceType.None)
                        {
                            N.PieceColor chessPieceColor = engine.GetPieceColorAt(column, row);
                            bool selected = currentSource.Row == row && currentSource.Column == column && currentSource.Selected;

                         
                            int x = (column) * boxHeight;                          
                            int y = (row) * boxHeight;

                            if (chessPieceColor == N.PieceColor.White)
                            {
                                if (chessPieceType == N.PieceType.Pawn)
                                {
                                    g.DrawImage(Resources.WPawn, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Rook)
                                {
                                    g.DrawImage(Resources.WRook, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Knight)
                                {
                                    g.DrawImage(Resources.WKnight, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Bishop)
                                {
                                    g.DrawImage(Resources.WBishop, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Queen)
                                {
                                    g.DrawImage(Resources.WQueen, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.King)
                                {
                                    g.DrawImage(Resources.WKing, x + buffer, y, boxHeight, boxHeight);
                                }
                            }
                            else
                            {
                                if (chessPieceType == N.PieceType.Pawn)
                                {
                                    g.DrawImage(Resources.BPawn, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Rook)
                                {
                                    g.DrawImage(Resources.BRook, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Knight)
                                {
                                    g.DrawImage(Resources.BKnight, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Bishop)
                                {
                                    g.DrawImage(Resources.BBishop, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.Queen)
                                {
                                    g.DrawImage(Resources.BQueen, x + buffer, y, boxHeight, boxHeight);
                                }
                                else if (chessPieceType == N.PieceType.King)
                                {
                                    g.DrawImage(Resources.BKing, x + buffer, y, boxHeight, boxHeight);
                                }
                            }

                            if (selected)
                            {
                                selectedX = ((column)*boxHeight) + buffer;
                                //selectedY = (8 - row - 1)*boxHeight;

                                //selectedX = ((8-column-1) * boxHeight) + buffer;
                                selectedY = (row) * boxHeight;

                                g.DrawRectangle(penHightlight, selectedX, selectedY, boxHeight - 1, boxHeight - 1);


                                //Draw Valid Moves
                                List<N.Move> validMoves = engine.GetMoves().Where(m => m.SrcPos1 == row * 8 + column).ToList();
                                if (validMoves.Count > 0)
                                {

                                    foreach (N.Move move in validMoves)
                                    {
                                        int moveY = (move.DestPos1 / 8) * boxHeight;
                                       
                                        int moveX = ((move.DestPos1 % 8) * boxHeight) + buffer;
                                        
                                        g.DrawRectangle(penValidMove, moveX, moveY, boxHeight - 1, boxHeight - 1);
                                    }
                                }
                            }
                            //if (engine.GetEnPassantMoves()[0] > 0)
                            //{
                            //    int moveY = (engine.GetEnPassantMoves()[1]) * boxHeight;

                            //    int moveX = (engine.GetEnPassantMoves()[0] * boxHeight) + buffer;
                                
                            //    g.DrawRectangle(penEnPassant, moveX, moveY, boxHeight - 1, boxHeight - 1);
                            //}

                        }
                    }
                }

                if (currentDestination.Selected)
                {
                    selectedY = (currentDestination.Row ) * boxHeight;
                    selectedX = ((currentDestination.Column) * boxHeight) + buffer;
                    
                    g.DrawRectangle(penDestination, selectedX, selectedY, boxHeight - 1, boxHeight - 1);
                }
         

                graphicsBuffer.Render(e.Graphics);

                g.Dispose();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error Drawing Chess Board", MessageBoxButtons.OK);
            }

        }

        private void ChessBoard_MouseClick(object sender, MouseEventArgs e)
        {
            if ((engine.WhoseMove == N.PieceColor.Black && _blackIsAI) ||
                (engine.WhoseMove == N.PieceColor.White && _whiteIsAI))
                return;

            byte column = 0;
            byte row = 0;

            try
            {
                //Get Column
                for (int i = 0; i < 8; i++)
                {
                    if (((i * boxHeight) + 10) < e.Location.X)
                    {
                        column++;
                    }
                    else
                    {
                        break;
                    }
                }

                //Get Row
                for (int i = 0; i < 8; i++)
                {
                    if (i*boxHeight < e.Location.Y)
                    {
                        row++;
                    }
                    else
                    {
                        break;
                    }
                }
                column--;

                row--;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error Calculating Selected Column and Row", MessageBoxButtons.OK);
            }

            //Check if row and column are within bounds
            if (column > 7 || column < 0)
            {
                return;
            }
            if (row > 7 || row < 0)
            {
                return;
            }

            try
            {
                if (currentSource.Column == column && currentSource.Row == row && currentSource.Selected)
                {
                    //Unselect current Selection
                    currentSource.Selected = false;
                    currentDestination.Selected = false;

                    //if (engine.GetPieceTypeAt(column, row) != N.PieceType.None)
                    //{
                    //    engine.SetChessPieceSelection(column, row, false);
                    //}
                }
                else if ((currentSource.Column != column || currentSource.Row != row) && currentSource.Selected)
                {
                    //Make Move
                    currentDestination.Selected = true;
                    currentDestination.Column = column;
                    currentDestination.Row = row;

                    EngineMove();
                }
                else
                {
                    if (engine.GetPieceTypeAt(column, row) != N.PieceType.None)
                    {        
                        if (engine.GetPieceColorAt(column, row) != engine.WhoseMove)
                        {
                            //engine.SetChessPieceSelection(currentDestination.Column, currentDestination.Row, false);

                            currentSource.Selected = false;
                            currentDestination.Selected = false;
                            return;
                        }
                        //engine.SetChessPieceSelection(column, row, true);
                    }
                    else
                    {
                        //engine.SetChessPieceSelection(currentDestination.Column, currentDestination.Row, false);

                        currentSource.Selected = false;
                        currentDestination.Selected = false;
                        return;
                    }

                    //Select Source
                    currentDestination.Selected = false;
                  
                    currentSource.Column = column;
                    currentSource.Row = row;
                    currentSource.Selected = true;

                    
                }

               

                Refresh();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error Selecting Chess Piece", MessageBoxButtons.OK);
            }
        }

        private void ChessBoard_Load(object sender, EventArgs e)
        {
            if (TurnChanged != null)
            {
                TurnChanged(engine.WhoseMove);
            }            
        }

        private void ChessBoard_Resize(object sender, EventArgs e)
        {
            Refresh();
        }

        #endregion
       
    }
}